/*
 * SpeedoDyno - Speedometer and dynamometer application for Android 
 * Copyright 2013 Guilhem VALENTIN
 * 
 * This file is part of SpeedoDyno
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with SpeedoDyno. If not, see <http://www.gnu.org/licenses/>.
 */
package com.gvalentin.speedodyno.view;

import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import com.gvalentin.speedodyno.R;
import com.gvalentin.speedodyno.data.SpeedoDynoData;
import com.gvalentin.speedodyno.util.PreferenceUtil;

/**
 * Dynamometer bars of separated power consumption areas
 * @author guilhem
 */
public class DynamoBars extends View {
	
	private static final String TAG = DynamoBars.class.getSimpleName();

	// Bars background dimensions
	// F: friction, D: aero drag, A: acceleration, E: elevation
	private static final float BAR_WIDTH = 0.08f;
	private static final float BAR_F_LEFT = 0.26f-BAR_WIDTH/2;
	private static final float BAR_F_RIGHT = 0.26f+BAR_WIDTH/2;
	private static final float BAR_D_LEFT = 0.42f-BAR_WIDTH/2;
	private static final float BAR_D_RIGHT = 0.42f+BAR_WIDTH/2;
	private static final float BAR_A_LEFT = 0.58f-BAR_WIDTH/2;
	private static final float BAR_A_RIGHT = 0.58f+BAR_WIDTH/2;
	private static final float BAR_E_LEFT = 0.74f-BAR_WIDTH/2;
	private static final float BAR_E_RIGHT = 0.74f+BAR_WIDTH/2;
	
	private static final float BAR_TOP = 0.05f;
	private static final float BAR_BOTTOM = 0.8f;
	private static final float BAR_HEIGHT = BAR_BOTTOM-BAR_TOP;
	
	private static final float BAR_ICON_WIDTH = 0.16f;
	private static final float BAR_ICON_TOP = 0.84f;
	

	// Data
	private SpeedoDynoData data = null;
	
	// View scale
	private float scaleX;
	private float scaleY;
	
	// Drawing tools
	private Paint barBackgroundPaint;
	private Paint barShadowPaint;
	private Paint barPositivePaint;
	private Paint barNegativePaint;
	private Paint bitmapPaint;
	private Paint backgroundPaint;

	/** holds the cached static part */	
	private Bitmap background;

	/** Initial maximum power in scale (from preferences) */
	private int initialMaxScalePower = 200;

	// Maximum power that can be displayed in scale (if maximum is reached, display sticks to maximum)
	private int maxScalePower = 200; // W
	
	
	/**
	 * Constructor
	 */
	public DynamoBars(Context context) {
		super(context);
		init();
	}

	/**
	 * Constructor
	 */
	public DynamoBars(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}

	/**
	 * Constructor
	 */
	public DynamoBars(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init();
	}


	/**
	 * Init configuration. This method must be called at activity initialization,
	 * and each time the preferences are modified.
	 */
	public void configure(SharedPreferences sharedPreferences) {
		maxScalePower = PreferenceUtil.getPreference(sharedPreferences, "pref_maxPower", 400) / 2;
		initialMaxScalePower = maxScalePower;
	}

	/** Initialize the view */
	private void init() {
		initDrawingTools();
	}

	/**
	 * Init the background of the view
	 */
	private void initDrawingTools() {
		// Paint for bar background
		barBackgroundPaint = new Paint();
		barBackgroundPaint.setAntiAlias(true);
		barBackgroundPaint.setStyle(Paint.Style.FILL);
		barBackgroundPaint.setColor(0xFF404040);
		
		// Rim shadow
		barShadowPaint = new Paint();
		barShadowPaint.setAntiAlias(true);
		barShadowPaint.setStyle(Paint.Style.FILL);
		
		// Paint for bar in positive values
		barPositivePaint = new Paint();
		barPositivePaint.setAntiAlias(true);
		barPositivePaint.setStyle(Paint.Style.FILL);
		barPositivePaint.setColor(Color.RED);
		barPositivePaint.setShadowLayer(0.02f, 0, 0, 0x80000000);
		barPositivePaint.setShader(new LinearGradient(0.0f, BAR_TOP+BAR_HEIGHT/4, 0.0f, BAR_BOTTOM,
				   new int[] { Color.YELLOW, Color.RED },
				   new float[] { 0.0f, 1.0f },
				   Shader.TileMode.CLAMP));
		
		// Paint for bar in negative values
		barNegativePaint = new Paint();
		barNegativePaint.setAntiAlias(true);
		barNegativePaint.setStyle(Paint.Style.FILL);
		barNegativePaint.setColor(Color.BLUE);
		barNegativePaint.setShadowLayer(0.02f, 0, 0, 0x80000000);
		barNegativePaint.setShader(new LinearGradient(0.0f, BAR_TOP, 0.0f, BAR_BOTTOM-BAR_HEIGHT/4,
				   new int[] { Color.BLUE, Color.CYAN },
				   new float[] { 0.0f, 1.0f },
				   Shader.TileMode.CLAMP));
		
		// Bitmap paint
		bitmapPaint = new Paint(); 
		bitmapPaint.setAntiAlias(true);
		bitmapPaint.setFilterBitmap(true);
		bitmapPaint.setDither(true);
		bitmapPaint.setARGB(128, 255, 255, 255);
		
		// Background
		backgroundPaint = new Paint();
		backgroundPaint.setFilterBitmap(true);
	}

	/**
	 * @see android.view.View#onMeasure(int, int)
	 */
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		Log.d(TAG, "Width spec: " + MeasureSpec.toString(widthMeasureSpec));
		Log.d(TAG, "Height spec: " + MeasureSpec.toString(heightMeasureSpec));

		// keep our custom view square, i.e. keep the width equal to the height 
		// regardless of what the MeasureSpec restrictions are
		int widthMode = MeasureSpec.getMode(widthMeasureSpec);
		int widthSize = MeasureSpec.getSize(widthMeasureSpec);
		
		int heightMode = MeasureSpec.getMode(heightMeasureSpec);
		int heightSize = MeasureSpec.getSize(heightMeasureSpec);
		
		int chosenWidth = chooseDimension(widthMode, widthSize);
		int chosenHeight = chooseDimension(heightMode, heightSize);
		
		int chosenDimension = Math.min(chosenWidth, chosenHeight);
		
		setMeasuredDimension(chosenDimension, chosenDimension);
	}
	
	/**
	 * Choose the size depending on the MeasureSpec restrictions
	 */
	private int chooseDimension(int mode, int size) {
		if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) {
			return size;
		} else { // (mode == MeasureSpec.UNSPECIFIED)
			return getPreferredSize();
		} 
	}
	
	/**
	 * in case there is no size specified
	 */
	private int getPreferredSize() {
		return 150;
	}

	/**
	 * @see android.view.View#onSizeChanged(int, int, int, int)
	 */
	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		Log.d(TAG, "Size changed to " + w + "x" + h);
		regenerateBackground();
	}
	
	/**
	 * Regenerate the background
	 */
	private void regenerateBackground() {
		// free the old bitmap
		if (background != null) {
			background.recycle();
		}
		
		background = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
		Canvas backgroundCanvas = new Canvas(background);
		scaleX = (float) getWidth();
		scaleY = (float) getHeight();
		backgroundCanvas.scale(scaleX, scaleY);
		
		drawBarsBackground(backgroundCanvas);
	}
	
	/**
	 * @see android.view.View#onDraw(android.graphics.Canvas)
	 */
	@Override
	protected void onDraw(Canvas canvas) {
		if (checkPowerScale()) {
			// No need to regenerate background
			//regenerateBackground();
		}
		
		drawBackground(canvas);
		
		if (data != null) {
			// Change the 2D coordinate space from width x height to 1.0 x 1.0
			// regardless of the current custom view size
			scaleX = (float) getWidth();
			scaleY = (float) getHeight();
			canvas.save(Canvas.MATRIX_SAVE_FLAG);
			canvas.scale(scaleX, scaleY);
			
			drawBar(canvas, BAR_F_LEFT, data.getPowerFriction());
			drawBar(canvas, BAR_D_LEFT, data.getPowerAero());
			drawBar(canvas, BAR_A_LEFT, data.getPowerAccel());
			drawBar(canvas, BAR_E_LEFT, data.getPowerElevation());
			
			canvas.restore();
		}
	}

	/**
	 * Check whether the power scale is OK for current power
	 * and update the max power scale if needed.
	 * Return true if the max power scale has been modified.
	 */
	private boolean checkPowerScale() {
		// Increase max scale if needed
		boolean needUpdate = false;
		if (data != null) {
			double powerAvg = data.getPowerAverage();
			// If max power scale is too high, reset to default
			if (maxScalePower > 2*powerAvg) {
				maxScalePower = initialMaxScalePower;
			}
			// Max should be at least the average power
			while (maxScalePower < powerAvg) {
				if (maxScalePower < 100) {
					maxScalePower += 20;
				} else if (maxScalePower < 200) {
					maxScalePower += 100;
				} else if (maxScalePower < 1000) {
					maxScalePower += 200;
				} else if (maxScalePower < 2000) {
					maxScalePower += 1000;
				} else if (maxScalePower < 10000) {
					maxScalePower += 2000;
				} else {
					maxScalePower += 10000;
				}
				needUpdate = true;
			}
		}
		return needUpdate;
	}
	
	/**
	 * Draw the background, by reusing pre-drawn bitmap
	 */
	private void drawBackground(Canvas canvas) {
		if (background == null) {
			Log.w(TAG, "Background not created");
		} else {
			canvas.drawBitmap(background, 0, 0, backgroundPaint);
		}
	}
	
	/**
	 * Draw the bars
	 */
	private void drawBarsBackground(Canvas canvas) {
		RectF r = new RectF();

		// Friction
		r.set(BAR_F_LEFT, BAR_TOP, BAR_F_RIGHT, BAR_BOTTOM);
		canvas.drawRect(r, barBackgroundPaint);
		drawIcon(canvas, R.drawable.ic_friction, r);
		drawShadow(canvas, r);
	     
		// Aerodynamic drag
		r.set(BAR_D_LEFT, BAR_TOP, BAR_D_RIGHT, BAR_BOTTOM);
		canvas.drawRect(r, barBackgroundPaint);
		drawIcon(canvas, R.drawable.ic_aerodrag, r);
		drawShadow(canvas, r);
		
		// Acceleration
		r.set(BAR_A_LEFT, BAR_TOP, BAR_A_RIGHT, BAR_BOTTOM);
		canvas.drawRect(r, barBackgroundPaint);
		drawIcon(canvas, R.drawable.ic_acceleration, r);
		drawShadow(canvas, r);
		
		// Elevation
		r.set(BAR_E_LEFT, BAR_TOP, BAR_E_RIGHT, BAR_BOTTOM);
		canvas.drawRect(r, barBackgroundPaint);
		drawIcon(canvas, R.drawable.ic_elevation, r);
		drawShadow(canvas, r);
	}
	
	/**
	 * Add shadow to bar
	 */
	private void drawShadow(Canvas canvas, RectF r) {
		barShadowPaint.setShader(getBarVerticalShader(r));
		canvas.drawRect(r, barShadowPaint);
		barShadowPaint.setShader(getBarHorizontalShader(r));
		canvas.drawRect(r, barShadowPaint);
	}
	
	/**
	 * Return vertical shader for bar
	 */
	private Shader getBarVerticalShader(RectF r) {
		float radius = 0.03f/BAR_HEIGHT;
		return new LinearGradient(0.5f, r.top, 0.5f, r.bottom,
				   new int[] { 0x80000500, 0x00000500, 0x00000000, 0x00000000, 0x00000500, 0x80000500 },
				   new float[] { 0.0f, radius, radius, 1.0f-radius, 1.0f-radius, 1.0f },
				   Shader.TileMode.MIRROR);
	}
	
	/**
	 * Return horizontal shader for bar
	 */
	private Shader getBarHorizontalShader(RectF r) {
		float radius = 0.03f/BAR_WIDTH;
		return new LinearGradient(r.left, 0.5f, r.right, 0.5f,
				   new int[] { 0x80000500, 0x00000500, 0x00000000, 0x00000000, 0x00000500, 0x80000500 },
				   new float[] { 0.0f, radius, radius, 1.0f-radius, 1.0f-radius, 1.0f },
				   Shader.TileMode.MIRROR);
	}
	
	/**
	 * Draw the specified icon left or right of the specified rect
	 */
	private void drawIcon(Canvas canvas, int id, RectF r) {
		Bitmap bitmap = BitmapFactory.decodeResource(getResources(), id);
		float bitmapScale = bitmap.getHeight()/BAR_ICON_WIDTH;
		//Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, size, size, true);
		canvas.save();
		canvas.scale(1/bitmapScale, 1/bitmapScale);
		float x = ((r.left+r.right)/2-BAR_ICON_WIDTH/2)*bitmapScale;
		float y = BAR_ICON_TOP*bitmapScale;
		canvas.drawBitmap(bitmap, x, y, bitmapPaint);
		canvas.restore();
	}
	
	/*
	 * Draw a positive/negative bar (acceleration, elevation)
	 */
	private void drawBar(Canvas canvas, float left, double power) {
		float height = BAR_HEIGHT;
		if (Math.abs(power) < maxScalePower) {
			height = (float) (height * power / maxScalePower);
		} else if (power < 0) {
			height = -height;
		}
		
		RectF r;
		if (height >= 0) {
			r = new RectF(left, BAR_TOP+BAR_HEIGHT-height, left+BAR_WIDTH, BAR_TOP+BAR_HEIGHT);
			canvas.drawRect(r, barPositivePaint);
		} else { // height < 0
			r = new RectF(left, BAR_TOP, left+BAR_WIDTH, BAR_TOP-height);
			canvas.drawRect(r, barNegativePaint);
		}
	}
	
	/**
	 * Update data
	 */
	public void setPositionData(SpeedoDynoData data) {
		this.data = data;
		invalidate();
	}
	
	/**
	 * Action to be performed when the preferences have been changed.
	 * Reset and redraw the whole display
	 */
	public void onPreferenceChanged(SharedPreferences sharedPreferences, String key) {
		if ("pref_maxPower".equals(key)) {
			configure(sharedPreferences);
			// Not needed to refresh background
			//regenerateBackground();
		}
	}
}
